home *** CD-ROM | disk | FTP | other *** search
/ HPAVC / HPAVC CD-ROM.iso / PXDTUT2.ZIP / PXDTUT2.TXT < prev   
Text File  |  1997-04-17  |  23KB  |  581 lines

  1.  
  2.                   |====================================|
  3.                   |                                    |
  4.                   |   TELEMACHOS proudly presents :    |
  5.                   |                                    |
  6.                   |    Part 2 of the PXD trainers  -   |
  7.                   |                                    |
  8.                   |           EMS-HANDLING             |
  9.                   |        the way to do it <g>        |
  10.                   |                                    |
  11.                   |====================================|
  12.  
  13.          ___---__-->   The Peroxide Programming Tips   <--__---___
  14.  
  15. <><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
  16.  
  17.  
  18. Intoduction
  19. -----------
  20.  
  21. Hiya... I'm Telemachos of Peroxide - a new danish group (yes... we DO have
  22. groups in Denmark too :)))  )
  23. In my last trainer "DOOM-walls : The technique and tricks" (which is really
  24. great, and I think you should leech it RIGHT away from the net :) ) I promised
  25. to do a trainer on the subject : Using the PC's memmory above the 640K limit
  26. which is set when using the standard memory-handling procedures provided with
  27. Turbo Pascal.
  28. Throughout this trainer I'll drop code in Pascal - with quite a load of asm
  29. code build in.
  30. But (as mentioned earlier.. uhmm.. like on line above this one) as most of
  31. the code is asm it should be easy to port to other languages like C/C++ or
  32. TASM (no kiddin' ?? :) ).
  33.  
  34. So, this trainer won't teach you to do colorful graphic effects that'll
  35. impress your friends and make your girlfriend proud of you.
  36. It will perhaps also be a bit boring when I start talking about the PC's
  37. hardware-limitations and stuff...
  38. But I promise you - it'll be well worth the reading time anyway as it'll give
  39. you the key to fast computer graphic - the key to storing ALL your data in
  40. memory before the program executes :)
  41.  
  42. So here we go.... "EMS-HANDLING : the way to do it :) "
  43.  
  44.  
  45. *************************** ATTENTION ARTISTS!!! *****************************
  46.  
  47. ARE YOU AN ARTIST ?
  48. DO YOU DRAW VGA-BITMAPS ?
  49. CAN YOU DO GFX IN ALL RESOLUTIONS - 320x200x256 AND VARIOUS SVGA-MODES ?
  50. DO YOU WANT TO SEE YOUR WORK IN AMAZING PRODUCTIONS ?
  51. CAN YOU DO GAME-GFX AS WELL AS BACKGROUNDS FOR DEMOS ?
  52.  
  53. IF YOU MEET THE ABOVE TERMS (or some at least :) ), THEN DROP ME (TELEMACHOS)
  54. A MESSAGE OR MAIL THE GROUP AT :  Peroxide@image.dk
  55.  
  56. ******************************************************************************
  57.  
  58.  
  59. If you want to get in contact with me, there are several ways of doing it :
  60.  
  61. 1) Write me via FIDO-net   :      2:235/350.22
  62.  
  63. 2) E-mail me               :      tm@image.dk
  64.  
  65. 3) Snail mail me           :      Kasper Fauerby
  66.                                   Saloparken 226
  67.                                   8300 Odder
  68.                                   Denmark
  69.  
  70. 4) Call me  (Voice ! )     :    +45 86 54 07 60
  71.  
  72.  
  73. Get this serie from the major demo-related FTP-sites or from our own homepage
  74. (soon at least ;) )        :  http://www.image.dk/~peroxide
  75. or directly from my own    :  http://www.image.dk/~tm
  76.  
  77.  
  78. Extended vs. expanded memory
  79. ----------------------------
  80.  
  81. We all know that the memory above 1MB can be either Extended (XMS) or Expanded
  82. (EMS) memory - but why do we need theese two "different" kind of memory types ?
  83. Why can't it all just be MEM ???
  84. The awnser lies all the way back when a guy called Bill Gates sat at the
  85. keyboard typing away on his main project - DOS!
  86. At that time 1MB of mem was a HUGE amount of mem.. so the guy never gave it
  87. a thought that perhaps someday even 1MB of mem would'nt be enough.
  88. He never thought that someday HE himself would release a OS that required
  89. 16MB of mem - just to run!!
  90. So.. DOS was made with a limitation of 1MB of mem. The first 640Kb being
  91. conventional memory - the rest being himem.
  92.  
  93. But as time went on and people started to make up more and more complicated
  94. programs, using more and more mem, it soon became aparent that if someone
  95. did'nt came up with a way of supporting more memory, then DOS would die.
  96. So... eventually some clever clever guys came up with the XMS and EMS
  97. specifications - each with it's own advantages and disadvantages.
  98.  
  99. The main reason why I have chosen to descripe the use of EMS memory is, that
  100. EMS-memory by far is the easiest himem to use.
  101. Its disadvantages (which I will descripe later) lies in the page-size, but
  102. with some clever codin' theese problems can be solved.
  103.  
  104.  
  105. What exactly is SEGMENTS and OFFSETS ?
  106. ---------------------------------------
  107.  
  108. DOS in real-mode is a 16bit environment. This means that DOS uses the old
  109. 16bit registers originating from the good ol' 8086/8088 processor.
  110. Well, to be able to address every single byte of memory we have in the computer
  111. each byte has to have a unique address.
  112. If we use only one register (16bit) for such an address some quick calculations
  113. reveals that the largest address available would be :
  114.  
  115. Largest address = 2^16 = 65536
  116.  
  117. Not very much ehh ??  Ie. using this model we can only address 64K of mem!!!!
  118. Even Bill Gates saw a problem in this and so he decided to use a few more bits
  119. to address the memory.
  120. Using 17 bits gives us a limit of 128K, 18 bits 256K, 19 bits 512Kb and finally
  121. the amount that Bill decided to use - namely 20 bits wich gives us a total of
  122. 1024 (1MB) of addressable memory.
  123.  
  124. But how do you address a 20bit memory location using only 16bit registers ?
  125. Well.. the awnser lies in dividing the memory into MEMORY SEGMENTS.
  126. To address a specific address in such a segment a 16bit OFFSET is used to
  127. determine how far IN in the SEGMENT the information is placed. This makes each
  128. segment 64K big.
  129.  
  130. Each SEGMENT also have a 16bit base address (fx. $a000 for the video card).
  131. When accessed the SEGMENT address is shifted 4 bits to the left - making it a
  132. 20bit value. The 16bit OFFSET address is now added to this 20bit address - the
  133. result being the final memory address.
  134. In theory a SEGMENT could begin at any given memory location, but as it's
  135. shifted 4 places to the left (multiplied by 16) all SEGMENT will begin at an
  136. address divisible by 16.
  137.  
  138. To sum things up we can have 2^16 = 65536 different segments within the 1MB
  139. boundry set by the 20bit system.
  140.  
  141. Lets take a look at the video card placed at $a000. This being a 16bit value
  142. we shift it 4 places to the left by multiplying it with 16. This gives us the
  143. memory location where the video card begin. $a000 shl 4 = $a0000 (hell... hex
  144. IS pretty neat huh ?? ) = 655360 bytes = 640K.
  145. WOW.. the card is JUST after the conventional memory :) So now you know what
  146. the memory from 640K to 704K does :)
  147.  
  148.  
  149. So every single byte of memory (below 1MB that is) is addressed through its
  150. SEGMENT and an OFFSET in this segment.
  151.  
  152. As an example lets take a look at the VGA screen again. Its SEGMENT being
  153. $a000 all we need to know is the offset. In mode 13h the memory layout is
  154. lineary - ie. the first pixel being at offset 0, the second at offset 1 and so
  155. on. Lets say we want to plot the 256th pixel on screen (coordinates ( 256,0)).
  156. Then the offset will be 256 = $100 - so the address desciped in 16bit registers
  157. is
  158.       pixel :   $a000:$0100
  159.  
  160.  
  161. Yeah right... I knew all that - but what about EMS?
  162. ---------------------------------------------------
  163.  
  164. EMS is - as mentioned earlier - just a name for the physical memory above the
  165. 1MB boundry.
  166. Actually this mem is only *made* EMS mem because of the LIM-specifications.
  167. The LIM expanded memory specifications is a software interface between the
  168. expanded memory manager (EMM) and the software that wishes to use the EMS
  169. memory. LIM supports up to 32M of EMS memory.
  170.  
  171. Because the computer (still talkin' real mode) can only physical address memory
  172. below 1M the EMS memory is handled through a WINDOW placed in himem - that is
  173. between 640K and 1024K.
  174. This window is called the page-frame. In the page-frame is several PHYSICAL EMS
  175. pages - each with the size of 16K - which I'll return to later.
  176. The EMS memory is divided into a number of LOGICAL pages... also with a size of
  177. 16K.
  178.  
  179. The trick is to map these LOGICAL pages into the PHYSICAL pages... and then
  180. address them as normal memory.
  181. You can look at the EMS memory as a giant piece of paper with information
  182. written on it - but what you actually see of this information is determined
  183. by the position of the "windows" in the page frame.
  184.  
  185. Take a look at this diagram :
  186.  
  187.  
  188.                       -----------------------------   up to 32MB
  189.                      |                             |
  190.                      |      EMS memory :           |
  191.                      |                             |
  192.                      |    divided into lots of     |
  193.                      |    16K blocks... LOGICAL    |
  194.                      |    pages!                   |
  195.                      |                             |
  196.                      |                             |
  197.                      |=============================|  1024K  (1MB)
  198.                   /  | / / / / / / / / / / / / / / |
  199.                 /    |-----------------------------|  960K
  200.    THIS IS    /      |       THE PAGE FRAME :      |
  201.    HIMEM!   /        |                             |
  202.             \        |     divided into 12 16K     |
  203.               \      |    physical memory blocks   |
  204.                 \    |-----------------------------|  768K
  205.                   \  | / / / / / / / / / / / / / / |
  206.                      |=============================|  640K
  207.                   /  |                             |
  208.                 /    |     24 16K physical pages   |
  209.               /      |     intended only for       |
  210.             /        |     operating system /      |
  211.  THIS IS  /          |     environment             |
  212.  CONVEN-  \          |-----------------------------|  256K
  213.  TIONAL     \        | / / / / / / / / / / / / / / |
  214.  MEMORY       \      | / / / / / / / / / / / / / / |
  215.                 \    | / / / / / / / / / / / / / / |
  216.                   \  |=============================|  0K
  217.  
  218.  
  219. Besides the page frame, himem is stuffed with things like the video memory and
  220. all sorts of devices and drivers you load with the LoadHigh and DeviceHigh
  221. commands in your autoexec.bat and config.sys.
  222. Now, this is why you have so much less himem to play with when running EMS -
  223. the page frame swallows quite a bit of memory :)
  224.  
  225.  
  226. Ok... Now I understand EMS - How do I code it ???
  227. --------------------------------------------------
  228.  
  229. Well.. the EMS functions are all controlled through int 67h.
  230. There are LOTS of functions, but I will only descripe the most important here.
  231. For a complete reference to all of the functions in int 67h get a copy of the
  232. file ems4spec.doc which is a complete transcription of the LIM-specifications.
  233. It is not included here with this tuturial as it's a 450Kb text file :)
  234.  
  235. I think the easiest way to understand the different basic EMS functions is to
  236. go through an EMS unit I have created for this tuturial. Each procedure /
  237. function will be explained as we go through them.
  238.  
  239. [ cut here for a rip of the unit TMEMS.PAS]
  240.  
  241. Unit TMEMS;
  242.  
  243. INTERFACE
  244.  
  245. Var Handle : integer;
  246. {by declaring this a public variable I assume that you will only be allocating
  247.  EMS pages ONCE during a program.
  248.  It saves you from having to pass the handle to a couple of functions every
  249.  time you call them.
  250.  But then you can't fx. allocate 10 pages at the beginning of the program -
  251.  and then allocate 15 more later.
  252.  If you wan't to do that you'll have to rewrite a couple of the routines so
  253.  that the correct handle must be passed to them when called.}
  254.  
  255. Function Hex_String (Number: Integer): String;
  256. Function EMS_AreYouThere : Boolean;
  257. Procedure Pagestatus(Var Total, Available: Integer);
  258. Procedure Allocate_Pages(Needed: Integer);
  259. Procedure Map_Page(Logical : Integer;Physical : byte);
  260. Function Get_Frame_Address : Integer;
  261. Procedure Deallocate_Pages;
  262. Function Get_Version_Number : string;
  263.  
  264. IMPLEMENTATION
  265.  
  266. Function Hex_String (Number: Integer): string;
  267.    Function Hex_Char (Number: Integer): Char;
  268.      Begin
  269.        If Number < 10 then  Hex_Char := Char (Number + 48)
  270.         else Hex_Char := Char (Number + 55);
  271.      end; { Function Hex_char }
  272.  
  273.    Var
  274.     S: string;
  275.    Begin
  276.     S := '';
  277.     S := Hex_Char ((Number shr 1) div 2048);
  278.     Number := (((Number shr 1) mod 2048) shl 1) + (Number and 1);
  279.     S := S + Hex_Char (Number div 256);
  280.     Number := Number mod 256;
  281.     S := S + Hex_Char (Number div 16);
  282.     Number := Number mod 16;
  283.     S := S + Hex_Char (Number);
  284.     Hex_String := S + 'h';
  285.    end; { Function Hex_String }
  286.  
  287. {uhm... this procedure is ripped from the file ems4spec.doc }
  288.  
  289.  
  290.  {***********************}
  291.  
  292. Function EMS_AreYouThere : Boolean;
  293.   Var
  294.    EMS_Name          : string[8];
  295.    Returned_Name     : string[8];
  296.    Position          : integer;
  297.    segm              : word;
  298.  
  299.   Begin
  300.    Returned_Name := '';
  301.    EMS_Name      := 'EMMXXXX0';     {this is the ID string that SHOULD appear
  302.                                      in the code segment of int 67h}
  303.     asm
  304.      mov ah,35h
  305.      mov al,67h
  306.      int 21h
  307.      mov segm,es
  308.     end;
  309.  
  310.    For Position := 0 to 7 do
  311.      Returned_Name := Returned_Name + Chr (mem[segm:Position + $0A]);
  312.  
  313.    {This call will return the segment address where the ID string SHOULD
  314.     be.
  315.     If the ID string is there it'll be placed from offset $0A to $11}
  316.  
  317.  
  318.    If Returned_Name = EMS_Name
  319.          then EMS_AreYouThere := true
  320.          else EMS_AreYouThere := false;
  321.   end; { Function EMS_AreYouThere }
  322.  
  323.  
  324.     {*******************}
  325.  
  326. Procedure Pagestatus(Var Total, Available: Integer);
  327.   Var
  328.    HowManyInAll     : word;
  329.    HowManyAvailable : word;
  330.   Begin
  331.     asm
  332.      mov ah,42h        {this is EMS function nr. 42h }
  333.      int 67h
  334.      mov HowManyAvailable,bx
  335.      mov HowManyInAll,dx
  336.     end;
  337.    Available:=HowManyAvailable;
  338.    Total:=HowManyInAll;
  339.   end; { Function Pagestatus }
  340.  
  341.  
  342. {This Procedure is nice when you want to know if there is enough free EMS
  343.  memory to run your program.}
  344.  
  345.  
  346.      {*************}
  347.  
  348. Procedure Allocate_Pages(Needed: Integer);
  349. Assembler;
  350.      asm
  351.       mov ah,43h             {this is EMS function nr. 43h}
  352.       mov bx,[Needed]
  353.       int 67h
  354.       mov [handle],dx        {NOT very nice... but heck... I like it this way}
  355.      end; { Function Allocate_Pages }
  356.  
  357. {When you run this procedure you allocate a certain amount of EMS pages.
  358.  A handle is then assigned to these pages. Those of you who code TASM know of
  359.  handles from file handling routines.
  360.  But those of you who just use Pascal are'nt used to having a number assigned
  361.  to a file or a piece of memory. Well.. its basicly the same thing as when you
  362.  use the Assign comand in TP. Here you assign a string - namely the filename -
  363.  to a var of the type : file (or file of bla bla bla). Later you use this var
  364.  when you want to manipulate with the file.
  365.  Same thing here - don't think about it.}
  366.  
  367.  
  368. {*****************}
  369.  
  370. procedure Map_Page(Logical : Integer;Physical : byte);
  371. Assembler;
  372.       asm
  373.        mov ah,44h                   {EMS function nr. 44h}
  374.        mov dx,[handle]              {humm... NO comments :) }
  375.        mov bx,[logical]
  376.        mov al,[Physical]
  377.        int 67h
  378.       end; { Function Map_Page }
  379.  
  380. {This procedure sets a window in the page frame to a certain logical page
  381.  in EMS memory.
  382.  From now on, when you manipulate with the physical page in the page frame
  383.  you manipulate with the mapped logical page in EMS}
  384.  
  385. {****************}
  386.  
  387.  
  388. Function Get_Frame_Address : Integer;
  389. Assembler;
  390.  asm
  391.   mov ah,41h    {EMS function nr. 41h}
  392.   int 67h
  393.   mov ax,bx
  394.  end;
  395.  
  396. {This function returns the segment address of the page frame.
  397.  Now this one is VERY!! important. When mapping logical pages into physical
  398.  pages the programmer needs to know where to address the physical pages.
  399.  Each physical page in the page frame has its own segement address.
  400.  Physical page nr. 0 has THE SAME ADDRESS AS THE PAGE FRAME.
  401.  From then on the physical pages are $400 (1Kb) apart - ie :
  402.   (say that the page frame address is $D000 - it often is )
  403.  
  404.      physical page nr.         segment address
  405.            0                      $D000
  406.            1                      $D400
  407.            2                      $D800
  408.            3                      $DC00
  409.            4                      $E000
  410. }
  411.  
  412. {*****************}
  413.  
  414. Procedure Deallocate_Pages;
  415. Assembler;
  416.      asm
  417.       mov ah,45h
  418.       mov dx,[handle]      {humm... keeps popping up everywhere   }
  419.       int 67h
  420.      end; { Procedure Deallocate_Pages }
  421.  
  422. {This returns the allocated EMS pages to the memory pool.
  423.  If you don't call this when closing a program down the EMS
  424.  memory will be useless to all other programs too }
  425.  
  426.  
  427. {************}
  428.  
  429. Function Get_Version_Number : String;
  430.   Var
  431.    Integer_Part, Fractional_Part: byte;
  432.  
  433.   Begin
  434.     asm
  435.      mov ah,46h
  436.      int 67h
  437.      cmp ah,0
  438.      jne @error
  439.      mov bl,al
  440.      shr bl,4
  441.      add bl,48
  442.      mov Integer_part,bl
  443.      mov bl,al
  444.      and bl,$F
  445.      add bl,48
  446.      mov Fractional_Part,bl
  447.   @error :
  448.     end;
  449.    Get_version_number:=chr(Integer_part)+'.'+chr(Fractional_part);
  450.   end; { Function Get_Version_Number }
  451.  
  452. {Ripped from ems4spec.doc - well.. the idea that is, rewritten to asm by ME <G>}
  453.  
  454.  
  455. [ end unit code ]
  456.  
  457.  
  458. Writing a program that uses EMS memory
  459. ---------------------------------------
  460. OK.. when writing a program that uses EMS memory there are certain things that
  461. you must ALWAYS do.
  462.  
  463. 1) Initialize the EMM manager. Call EMS_AreYouThere to see if EMS is available.
  464.  
  465. 2) Check if all the pages needed are available in the current system. You'll
  466.    have to try and calculate how many EMS pages your program is going to use.
  467.  
  468. 3) Allocate these pages and (if you don't use my unit) store the EMS handle in
  469.    some logically named variable.. like : "EMS_handle"  :)
  470.  
  471. 4) Get the address of the PAGE FRAME. Store this address in a variable - I
  472.    personally likes to use the name FADDR  (frame address).
  473.    When ever you want to write to EMS memory you do the following :
  474.  
  475.      Mem[Faddr+Phys_PageNr * $400 : offset_in_page] := value_to_be_stored;
  476.  
  477.    Voila! Now you have written to the LOGICAL EMS page that was mapped in
  478.    Phys_PageNr.
  479.    Often You'll find that you only have to use Physical Page nr 0...
  480.    If this is the case in your program simply use
  481.  
  482.      Mem[Faddr: offset_in_page] := Value_to_be_stored;
  483.  
  484.    In my Eye Of The Beholder demo from tuturial 1 I only use ONE physical page.
  485.  
  486. 5) Go ahead and use the memory. When writing to EMS map the correct page into
  487.    a physical page and write to this one - when reading from an EMS page map
  488.    the page to the physical page and read from this one.
  489.    It really IS as simple as this!
  490.  
  491. 6) When you are through using the EMS memory don't forget to deallocate the
  492.    used pages. Otherwise they will be unuseable by the system in any other
  493.    applications.
  494.  
  495.  
  496.  
  497.  
  498. Advantages / Disadvantages
  499. ---------------------------
  500.  
  501. The main advantage of using EMS memory is the ease of coding for it :)
  502. Really - with a few routines as those listed earlier in this tuturial you can
  503. code applications using up to 32MB of memory as easy as if it were all
  504. conventional memory. Play with the routines for an hour or so.... and you'll
  505. master EMS.
  506. Also EMS is fairly quick. Only an interrupt is required for mapping a LOGICAL
  507. EMS page into addressable memory. And with multiple physical pages available
  508. and some clever coding / structuring there should'nt be to many swaps in the
  509. program.
  510. Those of you who want to develop futher routines for handling EMS routines
  511. should take a look at the file ems4spec.doc - many more complicated functions
  512. are available through int 67h. Fx. I could mention that it IS possible to map
  513. multiple pages at the same time....
  514.  
  515. The main disadvantage of EMS memory is the page size. It really SUCK that an
  516. EMS pages originally was defined as 16K of memory. Why not 64K so that we
  517. could fit an entire segment into one page.
  518. As graphical programmers one of the first uses for EMS memory that we think of
  519. is storing graphic (well... I did anyway). Here a page size of 64K would be
  520. really cool as long as we stay in mode 13h. In that case we could store an
  521. entire screen in one EMS page...
  522. Well... unfortunately EMS pages is only 16K so we'll have to think of some
  523. other uses. One could split the screen up into 4 16K pieces... and then use
  524. 4 EMS pages pr. screen. But this is a little awkward as numerous interrupt
  525. calls must be made to map all four EMS pages needed to store the image.
  526. And when the image is to be used numerous physical_pages must be used - or the
  527. moving of data to the videocard must be split down into 4 parts.... one for
  528. each EMS page used.
  529.  
  530. But fortunately lots of other datastructures fit into a 16K block of memory.
  531. Textures fx. is typically 64X64 or 128X128 pixels in size - making them take
  532. up 4K or 16K of memory.
  533. In my 3d-world each level supports up to 100 different textures - each of the
  534. size 128X128. It would be far to slow to load them from disk when they were to
  535. be used in runtime. But 100 textures of 16K of mem each... they take up 1.6MB
  536. of memory!
  537. Also each level supports up to 20 different monsters using 5 frames (128X128)
  538. each to animate movement - that's 100 16K blocks more!
  539. So all in all my game takes up 3.2MB of memory - I could'nt have done it
  540. without EMS memory.
  541.  
  542. So - sprites in general, smaller bitmaps that does'nt take up the entire
  543. screen, lenses in demos, phongmaps, data structures for 3d objects - you name
  544. it.
  545. All those smaller structures often fit into an EMS page. When coding for EMS
  546. it is often required that you think about your structures when defining them.
  547. Perhaps you could split up some of your BIG records into several smaller ones ?
  548.  
  549.  
  550. Last remarks
  551. -------------
  552.  
  553. Well, that's about all for now.
  554. Hope you found this doc useful - and BTW : If you DO make anything public using
  555. these techniques please mention me in your greets or where ever you se fit.
  556. I DO love to see my name in a greeting :=)
  557.  
  558. What to do next???  Humm... I think I'll do something a little more colorfull
  559. next time - otherwise I'll just scare of my readers :)
  560. One guy requested bitmap rotation - another would like some more 3d-stuff...
  561. But one of these days I'm planning on doing a doc on interrupts - and how to
  562. use them. Comments ??
  563.  
  564. If you have any good ideas for a subject you wish to see a tuturial on please
  565. mail me. If I like the idea (and know anything about it :)  ) I'll write a
  566. tut on it.
  567.  
  568. Humm... yeah - while I remember it.
  569. These docs will soon be available through my very own homepage (linked to our
  570. group homepage) so check out the following addresses :
  571.  
  572.  
  573. Peroxide Homepage  :  http://www.image.dk/~peroxide   (this one is up by now)
  574. Telemachos' Page   :  http://www.image.dk/~tm         (SHOULD be up when you
  575.                                                        read this :)         )
  576.  
  577.  
  578. Keep on coding and CuL8'er M8's
  579.  
  580. Telemachos - April '97.
  581.